package com.boot2.core.reflect;

import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.*;

/**
 * 
 * @ClassName: BeanMap.java
 * @description: 自己实现的一个BeanMap，使用Invokers调用，最大限度提高性能
 * @author zhangweilin
 * @date 2018年1月31日 上午10:14:42
 *
 */
public class BeanMap extends AbstractMap<String, Object> implements Serializable {
	public static interface Getter {
		Object get(Object obj);
	}

	public static interface Setter {
		void set(Object obj, Object value);
	}

	public class PropertyMeta {
		PropertyDescriptor propertyDescriptor;
		String name;
		Getter reader;
		Setter writer;
		Class<?> type;
		private boolean hasReader;
		private boolean hasWriter;

		PropertyMeta(PropertyDescriptor propertyDescriptor) {
			this.propertyDescriptor = propertyDescriptor;
		}

		public String getName() {
			if (name == null) {
				name = propertyDescriptor.getName();
			}
			return name;
		}

		public Getter getReader() {
			if (reader == null && !hasReader) {
				Method method = propertyDescriptor.getReadMethod();
				if (method != null) {
					reader = Invokers.newInvoker(Getter.class, method.getDeclaringClass(), method.getName(),
							method.getParameterTypes(), method.getReturnType());
				}
				hasReader = true;
			}
			return reader;
		}

		public Setter getWriter() {
			if (writer == null && !hasWriter) {
				Method method = propertyDescriptor.getWriteMethod();
				if (method != null) {
					writer = Invokers.newInvoker(Setter.class, method.getDeclaringClass(), method.getName(),
							method.getParameterTypes(), method.getReturnType());
				}
				hasWriter = true;
			}
			return writer;
		}

		public Class<?> getPropertyType() {
			if (type == null) {
				type = propertyDescriptor.getPropertyType();
			}
			return type;
		}

	}

	public interface PropertyEntry<K, V> extends Entry<K, V> {
		public PropertyMeta getProperty();

		public Class<?> getType();

		public boolean canRead();

		public boolean canWrite();
	}

	/**
	 * 类字段映射
	 */
	private static final Map<Class<?>, Map<String, PropertyMeta>> classBeanMap = new WeakHashMap<Class<?>, Map<String, PropertyMeta>>();
	/** 
	 *  
	 */
	private static final long serialVersionUID = -3627407279602086245L;

	final private Object source;

	final private Class<?> sourceClass;

	private Map<String, PropertyMeta> propertyMetaMap;

	public BeanMap(Object source) {
		if (source == null) {
			throw new NullPointerException("The source object should't be null");
		}
		this.source = source;
		this.sourceClass = source.getClass();
		generateBeanMap();
	}

	@Override
	public Set<Entry<String, Object>> entrySet() {
		final Map<String, PropertyMeta> propertyMap = propertyMetaMap;
		Set<Entry<String, Object>> entrySet = new AbstractSet<Entry<String, Object>>() {
			@Override
			public Iterator<Entry<String, Object>> iterator() {
				final Iterator<PropertyMeta> propertyIterator = propertyMap.values().iterator();
				return new Iterator<Entry<String, Object>>() {
					@Override
					public boolean hasNext() {
						return propertyIterator.hasNext();
					}

					@Override
					public Entry<String, Object> next() {
						final PropertyMeta property = propertyIterator.next();
						return new PropertyEntry<String, Object>() {
							@Override
							public String getKey() {
								return property.getName();
							}

							@Override
							public Object getValue() {
								try {
									Getter read = property.getReader();
									Object value = read == null ? null : read.get(source);
									return value;
								} catch (Exception e) {
									throw wrapCause(e);
								}
							}

							@Override
							public Object setValue(Object value) {
								try {
									Setter write = property.getWriter();
									Getter read = property.getReader();
									Object old = read == null ? null : read.get(source);
									if (write != null) {
										write.set(source, value);
									}
									return old;
								} catch (Throwable e) {
									throw wrapCause(e);
								}
							}

							@Override
							public Class<?> getType() {
								return property.getPropertyType();
							}

							@Override
							public boolean canWrite() {
								return property.getWriter() != null;
							}

							@Override
							public PropertyMeta getProperty() {
								return property;
							}

							@Override
							public boolean canRead() {
								return property.getReader() != null;
							}
						};
					}

					@Override
					public void remove() {
						throw new UnsupportedOperationException();
					}
				};
			}

			@Override
			public int size() {
				return propertyMap.size();
			}
		};
		return entrySet;
	}

	public Map<String, Entry<String, Object>> entryMap() {
		final Map<String, PropertyMeta> propertyMap = propertyMetaMap;
		return new AbstractMap<String, Entry<String, Object>>() {

			@Override
			public int size() {
				return propertyMap.size();
			}

			@Override
			public boolean containsKey(Object key) {
				return propertyMap.containsKey(key);
			}

			@Override
			public Entry<String, Object> get(Object key) {
				final PropertyMeta property = propertyMap.get(key);
				return new PropertyEntry<String, Object>() {
					@Override
					public String getKey() {
						return property.getName();
					}

					@Override
					public Object getValue() {
						try {
							Getter read = property.getReader();
							Object value = read == null ? null : read.get(source);
							return value;
						} catch (Exception e) {
							throw wrapCause(e);
						}
					}

					@Override
					public Object setValue(Object value) {
						try {
							Setter write = property.getWriter();
							Getter read = property.getReader();
							Object old = read == null ? null : read.get(source);
							if (write != null) {
								write.set(source, value);
							}
							return old;
						} catch (Throwable e) {
							throw wrapCause(e);
						}
					}

					@Override
					public Class<?> getType() {
						return property.getPropertyType();
					}

					@Override
					public boolean canWrite() {
						return property.getWriter() != null;
					}

					@Override
					public PropertyMeta getProperty() {
						return property;
					}

					@Override
					public boolean canRead() {
						return property.getReader() != null;
					}
				};
			}

			@Override
			public Set<Entry<String, Entry<String, Object>>> entrySet() {
				Set<Entry<String, Entry<String, Object>>> entrySet = new AbstractSet<Entry<String, Entry<String, Object>>>() {

					@Override
					public int size() {
						return propertyMap.size();
					}

					@Override
					public Iterator<Entry<String, Entry<String, Object>>> iterator() {
						final Iterator<PropertyMeta> propertyIterator = propertyMap.values().iterator();
						return new Iterator<Entry<String, Entry<String, Object>>>() {
							@Override
							public boolean hasNext() {
								return propertyIterator.hasNext();
							}

							@Override
							public Entry<String, Entry<String, Object>> next() {

								return new Entry<String, Entry<String, Object>>() {
									PropertyMeta property = propertyIterator.next();

									@Override
									public String getKey() {
										return property.getName();
									}

									@Override
									public Entry<String, Object> getValue() {
										return new PropertyEntry<String, Object>() {
											@Override
											public String getKey() {
												return property.getName();
											}

											@Override
											public Object getValue() {
												try {
													Getter read = property.getReader();
													Object value = read == null ? null : read.get(source);
													return value;
												} catch (Exception e) {
													throw wrapCause(e);
												}
											}

											@Override
											public Object setValue(Object value) {
												try {
													Setter write = property.getWriter();
													Getter read = property.getReader();
													Object old = read == null ? null : read.get(source);
													if (write != null) {
														write.set(source, value);
													}
													return old;
												} catch (Throwable e) {
													throw wrapCause(e);
												}
											}

											@Override
											public Class<?> getType() {
												return property.getPropertyType();
											}

											@Override
											public boolean canWrite() {
												return property.getWriter() != null;
											}

											@Override
											public PropertyMeta getProperty() {
												return property;
											}

											@Override
											public boolean canRead() {
												return property.getReader() != null;
											}
										};
									}

									@Override
									public Entry<String, Object> setValue(
											Entry<String, Object> value) {
										throw new UnsupportedOperationException();
									}
								};
							}

							@Override
							public void remove() {
								throw new UnsupportedOperationException();
							}
						};
					}
				};
				return entrySet;
			}
		};
	}

	@Override
	public Set<String> keySet() {
		return propertyMetaMap.keySet();
	}

	private Map<String, PropertyMeta> generateBeanMap() {
		try {
			if (propertyMetaMap == null) {
				propertyMetaMap = classBeanMap.get(sourceClass);
				if (propertyMetaMap == null) {
					propertyMetaMap = new TreeMap<String, PropertyMeta>();
					PropertyDescriptor[] propertys = Introspector.getBeanInfo(sourceClass).getPropertyDescriptors();
					for (PropertyDescriptor property : propertys) {
						String name = property.getName();
						if ("class".equals(name)) {
							continue;
						}
						PropertyMeta propertyMeta = new PropertyMeta(property);
						propertyMetaMap.put(name, propertyMeta);
					}
					classBeanMap.put(sourceClass, propertyMetaMap);
				}
			}
			return propertyMetaMap;
		} catch (Throwable e) {
			throw wrapCause(e);
		}
	}

	@Override
	public int size() {
		return propertyMetaMap.size();
	}

	@Override
	public boolean isEmpty() {
		return propertyMetaMap.isEmpty();
	}

	@Override
	public boolean containsKey(Object key) {
		return propertyMetaMap.containsKey(key);
	}

	@Override
	public Object get(Object key) {
		try {
			PropertyMeta property = propertyMetaMap.get(key);
			Getter read = property.getReader();
			Object value = read == null ? null : read.get(source);
			return value;
		} catch (IllegalArgumentException e) {
			throw wrapCause(e);
		}
	}

	@Override
	public Object put(String key, Object value) {
		try {
			PropertyMeta property = propertyMetaMap.get(key);
			Setter write = property.getWriter();
			Getter read = property.getReader();
			Object old = read == null ? null : read.get(source);
			if (write != null) {
				write.set(source, value);
			}
			return old;
		} catch (IllegalArgumentException e) {
			throw wrapCause(e);
		}
	}

	/**
	 * 设置值（避免put中返回旧值的性能损失）
	 * 
	 * @param key
	 *            键
	 * @param value
	 *            值
	 */
	public void set(String key, Object value) {
		try {
			PropertyMeta property = propertyMetaMap.get(key);
			Setter write = property.getWriter();
			if (write != null) {
				write.set(source, value);
			}
		} catch (IllegalArgumentException e) {
			throw wrapCause(e);
		}
	}

	/**
	 * 包裹异常
	 * 
	 * @param cause
	 * @return
	 */
	public static RuntimeException wrapCause(Throwable cause) {
		if (cause instanceof RuntimeException) {
			return (RuntimeException) cause;
		}
		return new RuntimeException(cause);
	}
}